S3 に格納したリソースを CloudFront で配信する構成をCDKで作る
やりたいこと
S3 に置いてある JSON ファイルを CloudFront で配信し、CloudFrontを経由してのみアクセスできるように設定します。
使うもの
- Typescript
- AWS CDK
AWS リソースを作成するのに AWS CDK を使い言語はTypescript で記述します。
本記事で利用した環境は以下の通りです。
tsc -v Version 3.7.4 cdk version 1.57.0 (build 2ccfc50)
AWS リソースの作成
早速 AWS CDK を使って AWS リソースを作成しましょう。必要なリソースは S3 バケットと CloudFront です。
雛形の作成
からっぽのディレクトリを作成して、そこに CDK の雛形を作成します。
$ mkdir cdk $ cd cdk $ cdk init --language typescript
モジュールの追加
S3 と CloudFront のモジュールを追加します。S3 と CloudFront に加え IAM も利用するのでインストールしておきます。
$ npm install @aws-cdk/aws-cloudfront @aws-cdk/aws-s3 @aws-cdk/aws-iam
Watch Mode を ON にする
Typescript は Javascript へトランスパイルしてからデプロイする必要があるので Typescript コンパイラの Watch Mode を ON にしておくことをお勧めします。
シンタックスエラーがある場合リアルタイムで教えてくれるので便利です。
$ tsc -w
AWS リソースの定義
lib/cdk-stack.ts
にリソースを定義します。
S3 Bucket & Policy
バケットと CloudFrontOriginAccessIdentity を定義してそのあとに Policy を設定します。
// Create Bucket const myBucket = new s3.Bucket(this, "my-bucket"); // Create OriginAccessIdentity const oai = new cloudfront.OriginAccessIdentity(this, "my-oai"); // Create Policy and attach to mybucket const myBucketPolicy = new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["s3:GetObject"], principals: [ new iam.CanonicalUserPrincipal( oai.cloudFrontOriginAccessIdentityS3CanonicalUserId ), ], resources: [myBucket.bucketArn + "/*"], }); myBucket.addToResourcePolicy(myBucketPolicy);
principals
に設定したアクセス元からのみに S3 バケットのGetObject
権限を渡しています。上記のポリシーを設定することで、S3 バケットのオブジェクトは CloudFront を介してのみアクセスできるようになります。
ポリシーの中にOrigin
という単語がよく出てきますが、Origin = CloudFrontを介してアクセスしたいリソース
です。
参考:オリジンアクセスアイデンティティを使用して Amazon S3 コンテンツへのアクセスを制限する
CloudFront WebDistribution
次にCloudFrontのWebDistributionの設定をします。
// Create CloudFront WebDistribution new cloudfront.CloudFrontWebDistribution(this, "WebsiteDistribution", { viewerCertificate: { aliases: [], props: { cloudFrontDefaultCertificate: true, }, }, priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL, originConfigs: [ { s3OriginSource: { s3BucketSource: myBucket, originAccessIdentity: oai, }, behaviors: [ { isDefaultBehavior: true, minTtl: cdk.Duration.seconds(0), maxTtl: cdk.Duration.days(365), defaultTtl: cdk.Duration.days(1), pathPattern: "my-contents/*", }, ], }, ], errorConfigurations: [ { errorCode: 403, responsePagePath: "/index.html", responseCode: 200, errorCachingMinTtl: 0, }, { errorCode: 404, responsePagePath: "/index.html", responseCode: 200, errorCachingMinTtl: 0, }, ], });
originConfigs
s3OriginSource
: アクセスしたいリソースの格納先にS3を利用する場合はバケットを指定しますbehaviors
:pathPattern
にmy-contents/*
を指定しています。これでバケットのmy-contents/
ディレクトリ下の全てのリソースへアクセスを許可する設定になります
errorConfigurations
許可したパス以外へアクセスがあった際のルーティング設定です。今回はindex.html
へリダイレクトされるよう設定します。
CDK 全文
lib/cdk-stack.ts
import * as cloudfront from "@aws-cdk/aws-cloudfront"; import * as iam from "@aws-cdk/aws-iam"; import * as s3 from "@aws-cdk/aws-s3"; import * as cdk from "@aws-cdk/core"; export class CdkStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Create Bucket const myBucket = new s3.Bucket(this, "my-bucket"); // Create OriginAccessIdentity const oai = new cloudfront.OriginAccessIdentity(this, "my-oai"); // Create Policy and attach to mybucket const myBucketPolicy = new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["s3:GetObject"], principals: [ new iam.CanonicalUserPrincipal( oai.cloudFrontOriginAccessIdentityS3CanonicalUserId ), ], resources: [myBucket.bucketArn + "/*"], }); myBucket.addToResourcePolicy(myBucketPolicy); // Create CloudFront WebDistribution new cloudfront.CloudFrontWebDistribution(this, "WebsiteDistribution", { viewerCertificate: { aliases: [], props: { cloudFrontDefaultCertificate: true, }, }, priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL, originConfigs: [ { s3OriginSource: { s3BucketSource: myBucket, originAccessIdentity: oai, }, behaviors: [ { isDefaultBehavior: true, minTtl: cdk.Duration.seconds(0), maxTtl: cdk.Duration.days(365), defaultTtl: cdk.Duration.days(1), pathPattern: "my-contents/*", }, ], }, ], errorConfigurations: [ { errorCode: 403, responsePagePath: "/index.html", responseCode: 200, errorCachingMinTtl: 0, }, { errorCode: 404, responsePagePath: "/index.html", responseCode: 200, errorCachingMinTtl: 0, }, ], }); } }
上記をデプロイして動作確認をしてみましょう。
cdk deploy
動作確認
CloudFormationのリソースからS3バケットが作成されているのが確認できたらpathPattern
とS3の構成を合わせるためにS3バケットのCreate Folder
からmy-contents
というディレクトリを作成します。
以下のファイルを作成したパスへ置いて、CloudFrontからアクセスできるかを確認します。 URLはCloudFrontのマネジメントコンソールから見ることができます。
questions.json
{ "survey1": { "question": "すきなどうぶつをおしえてね", "options": [ "うさぎさん", "ぞうさん", "きりんさん", "いるかさん", "ねこさん" ] }, "survey2": { "question": "すきなフルーツをおしえてね", "options": ["もも", "いちご", "すいか", "パイナップル", "ぶどう"] } }
閲覧することができました。
バケットへ直接アクセスするとどうなるでしょう
アクセスできないようになっています。
指定していないパスへアクセスするとどうなるでしょうか
index.html
へリダイレクトされているのが確認できます。(index.htmlが存在しないのでNoSuchKeyになっています)